package lol.clann.minecraft.springboot.remap;

import lol.clann.minecraft.springboot.bukkit.plugin.AdapterContext;
import lol.clann.minecraft.springboot.bukkit.utils.BukkitUtils;
import lol.clann.minecraft.springboot.remap.context.RemapContext;
import lol.clann.minecraft.springboot.remap.model.ClassMapping;
import lol.clann.minecraft.springboot.remap.remappers.*;
import relocate.org.objectweb.asm.ClassReader;
import relocate.org.objectweb.asm.ClassWriter;
import relocate.org.objectweb.asm.commons.ClassRemapper;
import relocate.org.objectweb.asm.commons.Remapper;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author pyz
 * @date 2019/7/11 10:47 PM
 */
public class RemapUtils {

    public static final String nmsPrefix = "net.minecraft.server";
    public static ClassLoader sbpClassLoader;

    /**
     * 从classLoader搜索类文件,进行remap并返回加载后的类
     *
     * @param classLoader
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    public static SBPJarMapping jarMapping;
    private static final List<Remapper> remappers = new ArrayList<>();

    static {
        remappers.add(new NMSVersionRemapper());
//        nms -> mcp
        jarMapping = new SBPJarMapping();
        jarMapping.setInheritanceMap(new InheritanceMap());
        jarMapping.setFallbackInheritanceProvider(new SBPInheritanceProvider());
        if (RemapContext.enableNMSRemap) {
            Map<String, String> relocations = new HashMap<String, String>();
            relocations.put("net.minecraft.server", "net.minecraft.server." + BukkitUtils.getPackageVersion());
            try {
                jarMapping.loadMappings(
                        new BufferedReader(new InputStreamReader(AdapterContext.getRemappingResource().openStream())),
                        new MavenShade(relocations),
                        null, false);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
//            重定向slf4j到自己的实现,彻底避免冲突
        jarMapping.registerClassMapping("org/slf4j/LoggerFactory", "lol/clann/minecraft/springboot/bukkit/plugin/logger/LoggerFactory");
        SBPJarRemapper jarMapper = new SBPJarRemapper(jarMapping);
        remappers.add(jarMapper);
        jarMapping.initFastMethodMapping(jarMapper);
    }

    public static byte[] remapFindClass(String name, byte[] bs) throws IOException {
        if (RemapContext.printRemapPluginClass) {
            System.out.println("========= before remap ========= ");
            ASMUtils.printClass(bs);
        }
        for (Remapper remapper : remappers) {
            ClassReader reader = new ClassReader(bs);
            ClassWriter writer = new ClassWriter(0);
            ClassRemapper classRemapper = new SBPClassRemapper(writer, remapper);
            reader.accept(classRemapper, 0);
            writer.visitEnd();
            bs = writer.toByteArray();
            if (RemapContext.printRemapPluginClass) {
                System.out.println("========= after " + remapper.getClass().getSimpleName() + " remap ========= ");
                ASMUtils.printClass(bs);
            }
        }
        if (RemapContext.dumpRemapPluginClass) {
            ASMUtils.dump(Paths.get(System.getProperty("user.dir"), "dumpRemapPluginClass"), bs);
        }
        return bs;
    }

    public static String map(String typeName) {
        typeName = mapPackage(typeName);
        return jarMapping.classes.getOrDefault(typeName, typeName);
    }

    public static String reverseMap(String typeName) {
        ClassMapping mapping = jarMapping.byNMSInternalName.get(typeName);
        return mapping == null ? typeName : mapping.getNmsSrcName();
    }

    public static String reverseMap(Class clazz) {
        ClassMapping mapping = jarMapping.byMCPName.get(clazz.getName());
        return mapping == null ? ASMUtils.toInternalName(clazz) : mapping.getNmsSrcName();
    }

    public static String mapPackage(String typeName) {
        for (Map.Entry<String, String> entry : jarMapping.packages.entrySet()) {
            String prefix = entry.getKey();
            if (typeName.startsWith(prefix)) {
                return entry.getValue() + typeName.substring(prefix.length());
            }
        }
        return typeName;
    }

}
